import { dxSqliteDB as SqliteDB_base_class } from './libvbar-m-dxsqlitedb.so';
import dxMap from './dxMap.js';
import log from './dxLogger.js';
import std from './dxStd.js';
/**
 * SQLite Database Module (dxSqliteDB)
 *
 * @description
 * Provides a multi-instance, thread-safe interface to SQLite databases. It manages database
 * connections by their file path, allowing different parts of an application, including
 * different threads, to safely access and manipulate the same database file.
 *
 * @feature
 * - **Multi-Instance**: Manage multiple database files simultaneously based on their paths.
 * - **Thread-Safe**: Uses a C++ layer with a serialized transaction model, allowing safe
 *   database access from multiple JavaScript threads without manual locking.
 * - **Cross-Thread Coordination**: Leverages `dxMap` as a global registry to ensure that
 *   once a database is initialized in any thread, other threads can get a connection to it
 *   simply by calling `init()` with the same path.
 *
 * @threadingModel
 * Each JS thread (main or worker) has its own memory heap, so JS objects cannot be
 * directly shared. This module handles this by:
 * 1. Using a shared `dxMap` (`__dxSqliteDB_instances__`) to store the `path` of each
 *    initialized database, which acts as a global registry.
 * 2. Each thread maintains its own local cache of `dxSqliteDB` instances, keyed by path.
 * 3. A call to `init(path)` will first check the local cache, then the global registry,
 *    ensuring that each thread gets a valid local instance pointing to the shared
 *    database file.
 *
 * @example
 * // --- Main Thread ---
 * import dxSqliteDB from 'dxSqliteDB';
 * const dbPath = '/data/app.db';
 *
 * try {
 *   const db = dxSqliteDB.init(dbPath);
 *   db.exec(`
 *     CREATE TABLE IF NOT EXISTS events (
 *       id INTEGER PRIMARY KEY AUTOINCREMENT,
 *       type TEXT NOT NULL,
 *       timestamp INTEGER
 *     )
 *   `);
 *   db.exec(`INSERT INTO events (type, timestamp) VALUES ('start', ${Date.now()})`);
 * } catch (e) {
 *   log.error('Main thread DB error:', e.message);
 * }
 *
 * // --- Worker Thread ---
 * import dxSqliteDB from 'dxSqliteDB';
 * const dbPath = '/data/app.db';
 *
 * try {
 *   // Calling init() again with the same path is safe. It will return a cached
 *   // local instance or create a new one connected to the same shared database file.
 *   const db = dxSqliteDB.init(dbPath);
 *   if (db) {
 *     const events = db.select('SELECT * FROM events ORDER BY id DESC LIMIT 1');
 *     // events would be: [{ id: 1, type: 'start', timestamp: 1678886400000 }]
 *   }
 * } catch (e) {
 *   log.error('Worker thread DB error:', e.message);
 * }
 */

// A thread-local cache for database instances, keyed by path. This map is NOT shared between threads.
const localInstances = new Map();

// A shared, cross-thread registry using dxMap to store registered paths.
const globalRegistry = dxMap.get('__dxSqliteDB_instances__');


const dxSqliteDB = {
    /**
     * Initializes and returns a database instance for a given path.
     *
     * @description
     * This function is the single entry point for getting a database connection. It's safe
     * to call this function multiple times with the same path from any thread.
     *
     * - If an instance for this path already exists in the current thread's cache, it's returned immediately.
     * - If not, it checks a global registry. If another thread has already initialized this path,
     *   a new local instance is created for the current thread, connected to the same database file.
     * - If it's the first time this path is initialized by any thread, it will be registered globally.
     *
     * @param {string} path - The full, absolute path to the database file. This path is used as the unique identifier.
     * @returns {SqliteDB_base_class} The database instance object. This object has the following methods:
     *   - **.exec(sql: string): void** - Executes a non-query SQL statement (e.g., CREATE, INSERT, UPDATE, DELETE). Throws an error on failure.
     *   - **.select(sql: string): object[]** - Executes a query SQL statement (SELECT) and returns an array of result objects.
     *   - **.begin(): void** - Begins a new transaction.
     *   - **.commit(): void** - Commits the current transaction.
     *   - **.rollback(): void** - Rolls back the current transaction.
     * @throws {Error} Throws an error if the 'path' is missing or empty, or if the underlying database connection fails.
     */
    init: function (path) {
        if (!path) {
            throw new Error("dxSqliteDB.init requires a 'path'");
        }

        // 1. Check thread-local cache first.
        if (localInstances.has(path)) {
            return localInstances.get(path);
        }

        // 2. Check if the path is in the global registry (initialized by any thread).
        const isGloballyRegistered = globalRegistry.has(path);

        try {
            // Create a new local instance regardless.
            const dbInstance = new SqliteDB_base_class();
            std.ensurePathExists(path);
            dbInstance.init(path);

            // Store in the thread-local cache.
            localInstances.set(path, dbInstance);

            // If it wasn't globally registered before, register it now.
            if (!isGloballyRegistered) {
                globalRegistry.put(path, "1"); // Value indicates presence.
            }

            return dbInstance;
        } catch (e) {
            log.error(`Failed to initialize dxSqliteDB instance for path '${path}':`, e.message);
            // In case of failure, only clean up the global registry if this was the first attempt.
            if (!isGloballyRegistered) {
                globalRegistry.del(path);
            }
            throw e;
        }
    },

    /**
     * Closes the database connection for the current thread.
     *
     * @description
     * This function should be called when a database is no longer needed in the current thread.
     * It closes the database connection and cleans up resources for the local instance.
     * NOTE: This is a thread-local operation and does not affect the global registry.
     *
     * @param {string} path - The path of the database instance to deinitialize.
     */
    deinit: function (path) {
        if (!path) return;

        // Clean up the local instance if it exists in this thread.
        if (localInstances.has(path)) {
            const dbInstance = localInstances.get(path);
            try {
                dbInstance.deinit();
            } catch (e) {
                log.error(`Error during deinit of local dxSqliteDB instance for path '${path}':`, e.message);
            } finally {
                localInstances.delete(path);
            }
        }
    }
};

export default dxSqliteDB;